<template>
  <div class="adaptive-box">
    <div ref="adaptiveBoxRef" class="adaptive-box_body">
      <div ref="adaptiveContentRef" class="adaptive-box_content" :style="{ '--adaptive-height': fullHeight }">
        <slot />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { Ref } from 'vue'
import lodash from '@utils/lodash'

const props = withDefaults(defineProps<{
  reference?: HTMLElement | Window & typeof globalThis
  minWidth?: number,
  maxWidth?: number,
  updateWidth?: boolean,
  observeNodeUpdate?: boolean,
  // 等宽 | 包含 | 高度充满
  mode?: 'equal-width' | 'contain' | 'full-height' | string
}>(), {
  reference: undefined,
  minWidth: -Infinity,
  maxWidth: Infinity,
  updateWidth: false,
  observeNodeUpdate: false,
  mode: 'equal-width'
})

onMounted(function () {
  initReference()
  resize()
  initResizeMonitor()
})

onBeforeUnmount(function () {
  removeResizeMonitor()
})

// 参考对象
let _reference: HTMLElement | Window = null
const adaptiveBoxRef = ref() as Ref<HTMLDivElement>
const adaptiveContentRef = ref() as Ref<HTMLDivElement>

// region TODO 初始化
// 初始化参考对象
function initReference () {
  if (props.reference) {
    _reference = props.reference
  } else {
    _reference = window
  }
  resizeHandel = resizeType[props.mode]
}
// endregion

// region TODO 计算比例大小
const resizeType = {
  'equal-width': resizeIsEqualWidth,
  contain: resizeIsContain,
  'full-height': resizeIsFullHeight
}
let resizeHandel = null
// 重置大小
function resize () {
  resizeHandel && resizeHandel()
}

// TODO 绘制与参考对象等宽
function resizeIsEqualWidth () {
  // 参考宽度
  const referenceWidth = (_reference as HTMLElement).offsetWidth || (_reference as Window).innerWidth
  const $content = adaptiveContentRef.value
  let scale = referenceWidth / $content.offsetWidth

  let width = $content.offsetWidth * scale
  if (props.minWidth && width < props.minWidth) {
    scale = props.minWidth / $content.scrollWidth
    width = props.minWidth
  }
  if (props.maxWidth && width > props.maxWidth) {
    scale = props.maxWidth / $content.scrollWidth
    width = props.maxWidth
  }
  $content.style.transform = `scale(${scale})`
  // 计算缩放后所占的高度
  const height = $content.offsetHeight * scale
  adaptiveBoxRef.value.style.height = `${height}px`
  if (props.updateWidth) {
    adaptiveBoxRef.value.style.width = `${width}px`
  }
}

// TODO 绘制内容完全存在与参考对象中，左右居中
function resizeIsContain () {
  // 参考宽高
  let referenceWidth, referenceHeight
  if (window === _reference) {
    referenceWidth = _reference.innerWidth
    referenceHeight = _reference.innerHeight
  } else {
    referenceWidth = (_reference as HTMLElement).offsetWidth
    referenceHeight = (_reference as HTMLElement).offsetHeight
  }
  const $content = adaptiveContentRef.value
  let scale = referenceWidth / $content.offsetWidth
  let height = $content.offsetHeight * scale
  let width = $content.offsetWidth * scale
  if (height > referenceHeight) {
    scale = referenceHeight / $content.offsetHeight
    height = $content.offsetHeight * scale
    width = $content.offsetWidth * scale
  }
  $content.style.transform = `scale(${scale})`
  adaptiveBoxRef.value.style.height = `${height}px`
  adaptiveBoxRef.value.style.width = `${width}px`
}

// TODO 绘制与参考对象等宽，并释放与参考对象同等高度的空间
const fullHeight = ref('')
function resizeIsFullHeight () {
  // 参考宽高
  let referenceWidth, referenceHeight
  if (window === _reference) {
    referenceWidth = _reference.innerWidth
    referenceHeight = _reference.innerHeight
  } else {
    referenceWidth = (_reference as HTMLElement).offsetWidth
    referenceHeight = (_reference as HTMLElement).offsetHeight
  }
  const $content = adaptiveContentRef.value
  let scale = referenceWidth / $content.offsetWidth
  let width = $content.offsetWidth * scale
  if (props.minWidth && width < props.minWidth) {
    scale = props.minWidth / $content.scrollWidth
    width = props.minWidth
  }
  if (props.maxWidth && width > props.maxWidth) {
    scale = props.maxWidth / $content.scrollWidth
    width = props.maxWidth
  }
  $content.style.transform = `scale(${scale})`
  // 计算缩放后所占的高度
  const height = referenceHeight / scale
  adaptiveBoxRef.value.style.height = `${referenceHeight}px`
  adaptiveContentRef.value.style.height = fullHeight.value = `${height}px`
  if (props.updateWidth) {
    adaptiveBoxRef.value.style.width = `${width}px`
  }
}
// endregion

// region TODO Resize监听
function initResizeMonitor () {
  _reference === window ? window.addEventListener('resize', resize) : loadResizeObserver()
  props.observeNodeUpdate && observeNode()
}
function removeResizeMonitor () {
  window.removeEventListener('resize', resize)
  observer && observer.disconnect()
  resizeObserver && resizeObserver.disconnect()
}
// endregion

// region TODO 观察大小变化
let resizeObserver: ResizeObserver = null
function loadResizeObserver () {
  if (_reference !== window) {
    resizeObserver = new ResizeObserver(lodash.debounce(resize, 50))
    resizeObserver.observe(_reference as HTMLElement)
  }
}
// endregion

// region TODO 观察节点变化
let observer: MutationObserver | null = null
// 开启监听节点变化
function observeNode () {
  const config = {
    attributes: true,
    childList: true,
    subtree: true
  }
  observer = new MutationObserver(resize)
  observer.observe(adaptiveBoxRef.value, config)
}
// endregion

defineExpose({ resize })
</script>

<style lang="scss" scoped>
.adaptive-box_body {
  overflow: hidden;
  margin-left: auto;
  margin-right: auto;
}

.adaptive-box_content {
  transform-origin: left top;
  width: fit-content;
  --adaptive-height: initial;
}
</style>
